package nl.utwente.ewi.hmi.dtproxy;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Date;

import com.merl.diamondtouch.DtlibDevice;
import com.merl.diamondtouch.DtlibException;
import com.merl.diamondtouch.DtlibInputTframe;
import com.merl.diamondtouch.DtlibSegment;

/**
 * The DiamondTouchProxy performs as a simple proxy for the DiamondTouch. This proxy class
 * can be used to let several clients get information from the DiamondTouch or to run the
 * DiamondTouch driver in a virtual system, allowing for application deployment for non
 * Windows XP operating systems.
 * 
 * The proxy only propagates DtlibInputTframes with the interpolated field data. This information
 * is then broadcasted over UDP at the specified port. The format of the Datagrams is as follows :
 * 
 * The broadcasted packet has the form of :
 * 
 * [device id  ] long
 * [frame count] int
 * 
 * frame count times :
 *    [timestamp ] long
 *    [columns   ] int
 *    [rows      ] int
 *    [#xsegments] int
 *    [#ysegments] int
 *    
 *    x segment times :
 *        [startpos] int
 *        [maxpos  ] int
 *        [endpos  ] int
 *    
 *    y segment times :
 *        [startpos] int
 *        [maxpos  ] int
 *        [endpos  ] int
 *    
 * With 0 frames the packet has a header size of 12 bytes, 
 * every frame adds an additional 24 bytes and every
 * segment adds an additional 12 bytes.
 * 
 * For a densily packed packet with 8 frames, 100 x and 100 y segments the packet
 * will have a size of
 * 12 + 8 * 24 + 8 * (100 + 100) * 12 bytes = 19404 bytes, 
 * about 30% of the theoretical limit of UDP.
 * 
 * A normal table setup has about 4 frames with 8 segments per frame during normal usage,
 * this yields 12 + 4 * 24 + 8 * 8 * 12 = 972 bytes.
 * 
 * 
 * 
 * @author Michiel Hakvoort
 * @version 1.0
 *
 */
public class DiamondTouchProxy extends Thread {

	static {
		System.loadLibrary("jdt");
	}

	private final DtlibDevice device;
	private final int port;
	private final InetAddress host;

	public DiamondTouchProxy(DtlibDevice device, InetAddress host, int port) {
		this.device = device;
		this.host = host;
		this.port = port;
	}
	
	@Override
	public void run() {
		DatagramSocket socket;

		try {
			if(host.isMulticastAddress()) {
				socket = new MulticastSocket();
			} else {
				socket = new DatagramSocket();
			}
		} catch(IOException e) {
			throw new RuntimeException(e);
		}

		if(device.start(DtlibInputTframe.getClassInputclass()) != 0) {
			throw new RuntimeException("Could not start DiamondTouch device");
		}

		boolean canContinue = true;

		byte[] backingArray = new byte[65536];

		ByteBuffer outBuffer = ByteBuffer.wrap(backingArray);

		DatagramPacket packet = new DatagramPacket(backingArray, backingArray.length);

		packet.setAddress(host);
		packet.setPort(port);
		
		long prevTime = System.currentTimeMillis();
		
		while(canContinue) {
			long newTime = System.currentTimeMillis();

			if(newTime - prevTime > 1000) {
				prevTime = newTime;
				System.out.println(new Date());
			}

			outBuffer.rewind();
			
			final DtlibInputTframe[] frames;
			try {
				frames = (DtlibInputTframe[]) device.read();
			} catch (DtlibException e) {
				canContinue = false;
				continue;
			}

			outBuffer.putLong(device.getId());
			outBuffer.putInt(frames.length);
			
			for(int i = 0; i < frames.length; i++) {
				final DtlibInputTframe frame = frames[i];
				final DtlibSegment[] xSegments = frame.getXSegments();
				final DtlibSegment[] ySegments = frame.getYSegments();

				outBuffer.putLong(frame.getTimestampMs());
				outBuffer.putInt(frame.getColLimit() * DtlibInputTframe.INTERPOLATION_FACTOR);
				outBuffer.putInt(frame.getRowLimit() * DtlibInputTframe.INTERPOLATION_FACTOR);

				outBuffer.putInt(xSegments.length);
				outBuffer.putInt(ySegments.length);

				for(int x = 0; x < xSegments.length; x++) {
					final DtlibSegment xSegment = xSegments[x];

					outBuffer.putInt(xSegment.startPos);
					outBuffer.putInt(xSegment.maxSignalPos);
					outBuffer.putInt(xSegment.stopPos);

				}

				for(int y = 0; y < ySegments.length; y++) {
					final DtlibSegment ySegment = ySegments[y];

					outBuffer.putInt(ySegment.startPos);
					outBuffer.putInt(ySegment.maxSignalPos);
					outBuffer.putInt(ySegment.stopPos);

				}
			}
			
			packet.setLength(outBuffer.position());
			
			try {
				socket.send(packet);
			} catch (IOException e) {
				canContinue = false;
			}
		}

		try {
			socket.close();
		} catch(Exception e) {
		}

		try {
			device.stop();
		} catch(Exception e) {
		}

	}

	
	public static void main(String[] args) {
		int port = 10002;

		InetAddress host = null;;
		
		for (int i=0;i<args.length;i++) {
			if (args[i].equalsIgnoreCase("--target") || args[i].equalsIgnoreCase("-t")) {
				i++;

				if(i < args.length) {
					try {
						host = InetAddress.getByName(args[i]);
					} catch (UnknownHostException e) {
						System.out.println("Could not identify host \"" + args[i] + "\"");
						System.exit(1);
					}
				}
			} else if(args[i].equalsIgnoreCase("--port") || args[i].equalsIgnoreCase("-p")) {
				i++;

				if(i < args.length) {
					try {
						port = Integer.parseInt(args[i]);
					} catch(NumberFormatException e) {
						System.out.println("Invalid port \"" + args[i] + "\"");
						System.exit(1);
					}
				}
			}
		}

		if(host == null) {
			try {
				host = InetAddress.getLocalHost();
			} catch (UnknownHostException e) {
				System.out.println(e.getMessage());
				System.exit(1);
			}
		}

		DtlibDevice device;

		long[] deviceIds = DtlibDevice.getDeviceIds();

		if(deviceIds.length == 0) {
			System.out.println("No devices available");
			System.exit(1);
		}
		
		device = DtlibDevice.getDevice(deviceIds[0]);
		
		System.out.println("Starting proxy with device " + device.getId() + "...");

		DiamondTouchProxy proxy = new DiamondTouchProxy(device, host, port);

		proxy.start();
		
		System.out.println("Proxy started on port " + port);
	}
}
